import struct

#------------------------------------------------------------------------------

class Struct:
  @staticmethod
  def new_struct(data_format): return struct.Struct('<' + data_format)

  def __init__(self,**kwargs):
    for field,default in zip(self.fields,self.defaults):
      if field in kwargs: setattr(self,field,kwargs[field])
      else: setattr(self,field,default)

#------------------------------------------------------------------------------

class BinaryFile(file):
  def __init__(self,filename,mode = 'r'):
    file.__init__(self,filename,mode + 'b')

  def read_at(self,size,at):
    pos = self.tell()
    self.seek(at)
    s = self.read(size)
    self.seek(pos)
    return s
  
  def write_at(self,s,at):
    pos = self.tell()
    self.seek(at)
    self.write(s)
    self.seek(pos)

  # TODO: in multi byte encondigs zero can be more that one byte (fx utf-16)
  def read_cstring(self,encoding = 'ascii'):
    string = bytearray()
    while True:
      b = self.read(1)
      if b == b'\x00': break
      string.append(b)
    return string.decode(encoding)
    
  def write_cstring(self,s,encoding = 'ascii'):
    self.write((s + '\0').encode(encoding))
    
  def read_struct(self,struct_type):
    s = struct_type()
    data = s.struct.unpack(self.read(s.struct.size))
    for field,value in zip(s.fields,data): setattr(s,field,value)
    return s

  def read_struct_at(self,struct_type,at):
    s = struct_type()
    data = s.struct.unpack(self.read_at(s.struct.size,at))
    for field,value in zip(s.fields,data): setattr(s,field,value)
    return s

  def write_struct(self,s):
    data = [getattr(s,field) for field in s.fields]
    self.write(s.struct.pack(*data))

  def align(self,length):
    while self.tell() % length != 0: self.write_u8(0xFF)

  def read_s8(self): return struct.unpack('<b',self.read(1))[0]
  def read_u8(self): return struct.unpack('<B',self.read(1))[0]
  def read_s16(self): return struct.unpack('<h',self.read(2))[0]
  def read_u16(self): return struct.unpack('<H',self.read(2))[0]
  def read_s32(self): return struct.unpack('<l',self.read(4))[0]
  def read_u32(self): return struct.unpack('<L',self.read(4))[0]
  def read_f32(self): return struct.unpack('<f',self.read(4))[0]

  def read_s8_at(self,at): return struct.unpack('<b',self.read_at(1,at))[0]
  def read_u8_at(self,at): return struct.unpack('<B',self.read_at(1,at))[0]
  def read_s16_at(self,at): return struct.unpack('<h',self.read_at(2,at))[0]
  def read_u16_at(self,at): return struct.unpack('<H',self.read_at(2,at))[0]
  def read_s32_at(self,at): return struct.unpack('<l',self.read_at(4,at))[0]
  def read_u32_at(self,at): return struct.unpack('<L',self.read_at(4,at))[0]
  def read_f32_at(self,at): return struct.unpack('<f',self.read_at(4,at))[0]

  def write_s8(self,v): self.write(struct.pack('<b',v))
  def write_u8(self,v): self.write(struct.pack('<B',v))
  def write_s16(self,v): self.write(struct.pack('<h',v))
  def write_u16(self,v): self.write(struct.pack('<H',v))
  def write_s32(self,v): self.write(struct.pack('<l',v))
  def write_u32(self,v): self.write(struct.pack('<L',v))
  def write_f32(self,v): self.write(struct.pack('<f',v))

  def write_s8_at(self,v,at): self.write_at(struct.pack('<b',v),at)
  def write_u8_at(self,v,at): self.write_at(struct.pack('<B',v),at)
  def write_s16_at(self,v,at): self.write_at(struct.pack('<h',v),at)
  def write_u16_at(self,v,at): self.write_at(struct.pack('<H',v),at)
  def write_s32_at(self,v,at): self.write_at(struct.pack('<l',v),at)
  def write_u32_at(self,v,at): self.write_at(struct.pack('<L',v),at)
  def write_f32_at(self,v,at): self.write_at(struct.pack('<f',v),at)

#------------------------------------------------------------------------------

class ByteArray(bytearray):
  def __init__(self,length = 0): bytearray.__init__(self,b'\x00'*length)

  def read_at(self,size,at): return bytes(self[at:at + size])
  def write_at(self,s,at): self[at:at + len(s)] = s

  def write_cstring(self,s,encoding = 'ascii'):
    self.extend((s + '\0').encode(encoding))

  def read_s8_at(self,at): return struct.unpack('>b',self.read_at(1,at))[0]
  def read_u8_at(self,at): return struct.unpack('>B',self.read_at(1,at))[0]
  def read_s16_at(self,at): return struct.unpack('>h',self.read_at(2,at))[0]
  def read_u16_at(self,at): return struct.unpack('>H',self.read_at(2,at))[0]
  def read_s32_at(self,at): return struct.unpack('>l',self.read_at(4,at))[0]
  def read_u32_at(self,at): return struct.unpack('>L',self.read_at(4,at))[0]
  def read_f32_at(self,at): return struct.unpack('>f',self.read_at(4,at))[0]

  def write_s8_at(self,v,at): self.write_at(struct.pack('>b',v),at)
  def write_u8_at(self,v,at): self.write_at(struct.pack('>B',v),at)
  def write_s16_at(self,v,at): self.write_at(struct.pack('>h',v),at)
  def write_u16_at(self,v,at): self.write_at(struct.pack('>H',v),at)
  def write_s32_at(self,v,at): self.write_at(struct.pack('>l',v),at)
  def write_u32_at(self,v,at): self.write_at(struct.pack('>L',v),at)
  def write_f32_at(self,v,at): self.write_at(struct.pack('>f',v),at)

#______________________________________________________________________________
